//
//  pj_fragments_alloc.h
//  pj-lib
//
//  Created by Pang Jun on 2/24/13.
//  Copyright (c) 2013 Pang Jun. All rights reserved.
//

#ifndef pj_lib_pj_fragments_alloc_h
#define pj_lib_pj_fragments_alloc_h

#define MINBYTES 8
#define MAXLISTNUM 16
#define THUNCK_SIZE 128

#include "pj_thuncks_alloc.h"

class FragmentsAlloc
{
private:
    union NodeObj
    {
        union NodeObj* freeListPtr;
        char clientData[1];
    };
    static NodeObj* volatile freeList[MAXLISTNUM];
    static int UpToChunck(long bytes)
    {
        return ((bytes + MINBYTES - 1)& ~(MINBYTES - 1));
    }
    static int FreeListIndex(long bytes)
    {
        return (bytes + MINBYTES - 1) / (MINBYTES - 1);
    }
private:
    static void * ChunkForListAlloc(long size);
    static char * ChunkAlloc(long size, int& chunksNum);
    static char * poolStartFree;
    static char * poolEndFree;
    static long heapSizeAllocated;
    
public:
    static void* allocate(long size)
    {
        NodeObj* volatile* myFreeList;
        NodeObj* result;
        myFreeList = freeList + FreeListIndex(size);
        result = *myFreeList;
        if (result == 0)
        {
            return ChunkForListAlloc(UpToChunck(size));
        }
        *myFreeList = result->freeListPtr;
        return result;
    }
    static void deallocate(void* ptr, long size)
	{
		NodeObj* volatile* myFreeList = freeList + FreeListIndex(size);
		NodeObj* bFreePtr = (NodeObj*)ptr;
		bFreePtr->freeListPtr = *myFreeList;
		*myFreeList = bFreePtr;
	}
    static void* reallocate(void* ptr, long old_size, long new_size)
	{
		void* result;
		int copySize;
		if (UpToChunck(old_size) == UpToChunck(new_size))
		{
			return (ptr);
		}
		result = allocate(new_size);
		copySize = new_size > old_size? old_size : new_size;
		memcpy(result, ptr, copySize);
		deallocate(ptr, old_size);
		return(result);
	}
};

char * FragmentsAlloc::ChunkAlloc(long size, int& chunksNum)
{
    char* result;
    long totalBytes = size * chunksNum;
    long poolRestBytes = poolEndFree - poolStartFree;
    if (poolRestBytes >= totalBytes)
    {
        result = poolStartFree;
        poolStartFree += totalBytes;
        return result;
    }
    else if(poolRestBytes >= size)
    {
        chunksNum = (int)(poolRestBytes/size);
        totalBytes = size * chunksNum;
        result = poolStartFree;
        poolStartFree += totalBytes;
        return result;
    }
    else
    {
        long bytesToHeapAlloc = 2 * totalBytes + UpToChunck(heapSizeAllocated >> 4);
        if (poolRestBytes > 0)
        {
            NodeObj* volatile* myFreeList = freeList + FreeListIndex(poolRestBytes);
            ((NodeObj*)poolStartFree)->freeListPtr = *myFreeList;
            *myFreeList = (NodeObj*)poolStartFree;
        }
        poolStartFree = (char*)malloc(bytesToHeapAlloc);
        if (0 == poolStartFree)
        {
            long i;
            NodeObj* volatile* myFreeList;
            NodeObj* ptr;
            for (i = size; i <= THUNCK_SIZE; i += MINBYTES)
            {
                myFreeList = freeList + FreeListIndex(i);
                ptr = *myFreeList;
                if(0 != ptr)
                {
                    *myFreeList = ptr->freeListPtr;
                    poolStartFree = (char*)ptr;
                    poolEndFree = poolStartFree + i;
                    return (ChunkAlloc(size, chunksNum));
                }
            }
            poolEndFree = 0;
            poolStartFree = (char*)ChunksAlloc::allocate(bytesToHeapAlloc);
        }
        heapSizeAllocated += bytesToHeapAlloc;
        poolEndFree = poolStartFree + bytesToHeapAlloc;
        return (ChunkAlloc(size, chunksNum));
    }
}

void * FragmentsAlloc::ChunkForListAlloc(long size)
{
    int chunksNum = 20;
    char * chunck = ChunkAlloc(size, chunksNum);
    NodeObj* volatile* myFreeList;
    NodeObj* result;
    NodeObj* currentNodeObj;
    NodeObj* nextNodeObj;
    int i;
    
    if (1 == chunksNum)
    {
        return chunck;
    }
    myFreeList = freeList + FreeListIndex(size);
    result = (NodeObj*)chunck;
    *myFreeList = nextNodeObj = (NodeObj*)(chunck + size);
    for (i = 1; ; i++)
    {
        currentNodeObj = nextNodeObj;
        nextNodeObj = (NodeObj*)((char*)nextNodeObj + size);
        if (chunksNum - 1 == i)
        {
            currentNodeObj->freeListPtr = 0;
            break;
        }
        else
        {
            currentNodeObj->freeListPtr = nextNodeObj;
        }
    }
    return (result);
}

FragmentsAlloc::NodeObj* volatile FragmentsAlloc::freeList[MAXLISTNUM];
char * FragmentsAlloc::poolStartFree = 0;
char * FragmentsAlloc::poolEndFree = 0;
long FragmentsAlloc::heapSizeAllocated = 0;


#endif
